package com.ideaaedi.log4j2.defender.defender;

import com.ideaaedi.log4j2.defender.constant.DefenderConstant;
import com.ideaaedi.log4j2.defender.strategy.DefenderStrategy;
import com.ideaaedi.log4j2.defender.util.DefenderUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.message.Message;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

/**
 * 字符串脱敏处理器实现
 *
 * @author JustryDeng
 * @since 2021/7/21 1:15:15
 */
public class DefaultStringMessageDefender extends AbstractLog4j2MessageDefender<String> {
    
    /**
     * 前分割符， 通过判断key前面那个字符，来判断此key是单独的，而不是某个单词的一部分，如: abccustomId    此customId不是key (customId      此customId是key abc
     * customId   此customId是key ，customId     此customId是key
     * <p>
     * 匹配 【空格】、【制表符】、【换行符】、【回车符】、【(】、【（】、【{】、【,】、【，】、【"】
     */
    protected static final Pattern PRE_DELIMITER_PATTERN = Pattern.compile("[\\s\\t\\r\\n(（{,，\"]+");
    
    /** 空格匹配 */
    protected static final Pattern SPACE_PATTERN = Pattern.compile("\\s*");
    
    /**
     * 是否兼容反斜杠
     * <p>
     *     举例说明： <br/>
     *     开启前: 能识别{"k":"v"}，但不能识别{\"k\":\"v\"}<br/>
     *     开启后: 能识别{"k":"v"}，也能识别{\"k\":\"v\"}
     * </p>
     */
    private final boolean compatBackslash;
    
    /**
     * 能是别的，key-value之间的分隔符
     */
    private final Set<KeyValueDelimiter> keyValueDelimiter;
    
    public DefaultStringMessageDefender(Map<String, DefenderStrategy> strategies, List<Log4j2MessageDefender> plugins,
                                        boolean compatBackslash, Set<KeyValueDelimiter> keyValueDelimiter) {
        super(strategies, plugins);
        this.compatBackslash = compatBackslash;
        if (CollectionUtils.isEmpty(keyValueDelimiter)) {
            throw new IllegalArgumentException("DefaultStringMessageDefender's keyValueDelimiter cannot be empty.");
        }
        this.keyValueDelimiter = keyValueDelimiter;
    }
    
    /**
     * <ul>
     *     <li>
     *         此脱敏方式，适用于: 1、key-value 与 key-value之间必须使用英文逗号【,】分割,否者不会脱敏
     *     </li>
     *     <li>
     *         举例说明："abcJsonString":"{\"key1\":\"value1\",\"key2\":\"value2\"}" 如果脱敏字段
     *                  有abcJsonString、key1，但是字段值是一个json样式的字符串值(如上)， 那么不会对整个字符串值进
     *                  行脱敏，而是对粒度最小的key-value进行脱敏，<br/>脱敏后形如: "abcJsonString":"{\"key1\":\"v****1\",\"key2\":\"value2\"}"
     *     </li>
     *     <li>
     *         建议：脱敏字段不要是集合或对象。 如果是，那么当中间的分隔符【:】时，脱敏正常; 当中间的分隔符， 可以是
     *         等号【=】 或  箭头符号【->】 或 前进符号【=>】时， 会有一点小小的bug缺陷，那就是，会对 集合/对象里第
     *         一个逗号前的数据进行脱敏， <br/>如: emailList=[1**********@qq.com, 13548417409@163.com, dengshuai9527@idea-aedi.com]
     *     </li>
     * </ul>
     */
    @Override
    public void doDesensitize(Message message, StringBuilder buffer,
                              Map<String, DefenderStrategy> strategyMap) {
        String strData = message.getFormattedMessage();
        if (StringUtils.isBlank(strData)) {
            return;
        }
        int initLength = buffer.length();
        buffer.append(strData);
        // 此时strData的值应与buffer.toString()的值一样
        strData = buffer.toString();
        int messageLength = strData.length();
        String keyValueStr;
        // 一个flag, 代表了本次key-value的脱敏方式
        final KeyValueDelimiter[] strideFlag = new KeyValueDelimiter[1];
        for (String targetField : strategyMap.keySet()) {
            for (int currKeyCurrIndex = initLength; currKeyCurrIndex < messageLength; ) {
                strideFlag[0] = null;
                int keyStartIndex = strData.indexOf(targetField, currKeyCurrIndex);
                if (keyStartIndex == -1) {
                    currKeyCurrIndex = messageLength;
                    continue;
                }
                boolean skip =
                        keyStartIndex != 0 && !PRE_DELIMITER_PATTERN.matcher(strData.charAt(keyStartIndex - 1) + "").matches();
                if (skip) {
                    currKeyCurrIndex = keyStartIndex + targetField.length();
                    continue;
                }
                int commaIndex = strData.indexOf(",", keyStartIndex + 1);
                if (commaIndex == -1) {
                    commaIndex = messageLength;
                }
                keyValueStr = strData.substring(keyStartIndex, commaIndex);
                doDesensitization(keyValueStr, targetField, keyStartIndex, buffer, strategyMap.get(targetField),
                        strideFlag);
                
                // 如果处理方式是json, 那么步长为key的长度, 否者步长为key-value的长度
                if (strideFlag[0] == KeyValueDelimiter.COLON) {
                    currKeyCurrIndex = keyStartIndex + targetField.length();
                } else {
                    currKeyCurrIndex = keyStartIndex + keyValueStr.length();
                }
            }
        }
    }
    
    /**
     * 脱敏
     *
     * @param keyAndValueStr
     *         本次要脱敏的 key-value， 如: customId = 123
     * @param keyStr
     *         本次要脱敏的key-value的key
     * @param keyStartIndex
     *         key的位置
     * @param sb
     *         脱敏(后的)数据容器
     * @param strategy
     *         脱敏的策略
     * @param strideFlag
     *         一个长度为1的数组, 用于记录本次定位key-value中value的方式
     */
    protected void doDesensitization(String keyAndValueStr, String keyStr, int keyStartIndex,
                                     StringBuilder sb, DefenderStrategy strategy,
                                     KeyValueDelimiter[] strideFlag) {
        int keyLength = keyStr.length();
        String valueStr = getValue(keyAndValueStr, keyLength, strideFlag);
        if (valueStr == null) {
            return;
        }
        int valueStrStartRelativeIndex = keyAndValueStr.indexOf(valueStr, keyLength);
        String valueAfterDesensitization = DefenderUtil.doReplace(valueStr, strategy);
        int replaceStartIndex = keyStartIndex + valueStrStartRelativeIndex;
        int replaceEndIndex = replaceStartIndex + valueStr.length();
        sb.replace(replaceStartIndex, replaceEndIndex, valueAfterDesensitization);
    }
    
    /**
     * 从键值字符串中，获取到value。
     *
     * @param keyAndValueStr
     *         键值字符串， 如: customId = 123
     * @param keyLength
     *         键的长度
     * @param strideFlag
     *         一个长度为1的数组, 用于记录本次定位key-value中value的方式
     *
     * @return 返回value值。 如果返回null, 说明无法正常获取到value。
     */
    protected String getValue(String keyAndValueStr, int keyLength, KeyValueDelimiter[] strideFlag) {
        if (keyAndValueStr.length() <= keyLength) {
            return null;
        }
        
        boolean isValid;
        String delimiter;
        /// 以 箭头符号【->】 为key-value分隔符
        if (keyValueDelimiter.contains(KeyValueDelimiter.ARROW)) {
            delimiter = "->";
            int arrowSignIndex = keyAndValueStr.indexOf(delimiter);
            if (arrowSignIndex != -1) {
                // 排除value值中的 箭头符号对逻辑的影响(简单实现)
                isValid = SPACE_PATTERN.matcher(keyAndValueStr.substring(keyLength, arrowSignIndex)).matches();
                if (isValid) {
                    strideFlag[0] = KeyValueDelimiter.ARROW;
                    return keyAndValueStr.substring(arrowSignIndex + delimiter.length()).trim();
                }
            }
        }
        
        /// 以 等于符号【=】 为key-value分隔符
        if (keyValueDelimiter.contains(KeyValueDelimiter.EQUAL)) {
            delimiter = "=";
            int equalSignIndex = keyAndValueStr.indexOf(delimiter);
            if (equalSignIndex != -1) {
                // 排除value值中的等号对逻辑的影响(简单实现)
                isValid = SPACE_PATTERN.matcher(keyAndValueStr.substring(keyLength, equalSignIndex)).matches();
                if (isValid) {
                    strideFlag[0] = KeyValueDelimiter.EQUAL;
                    return keyAndValueStr.substring(equalSignIndex + delimiter.length()).trim();
                }
            }
        }
    
        /// json字符串形式的key-value分隔
        if (keyValueDelimiter.contains(KeyValueDelimiter.COLON)) {
            delimiter = ":";
            int colonSignIndex = keyAndValueStr.indexOf(delimiter);
            // 如果是json格式的话，key后面紧接着的应该是【"】
            boolean nextCharIsLegalAfterKey = '\"' == keyAndValueStr.charAt(keyLength);
            int length = 1;
            boolean isBackslashQuotation = false;
            if (!nextCharIsLegalAfterKey && colonSignIndex != -1 && compatBackslash) {
                String tmpStr = keyAndValueStr.substring(keyLength, colonSignIndex).trim();
                if (DefenderConstant.BACKSLASH_QUOTATION_SIGN.equals(tmpStr)) {
                    length = 2;
                    isBackslashQuotation = true;
                    nextCharIsLegalAfterKey = true;
                }
            }
            if (colonSignIndex != -1 && nextCharIsLegalAfterKey) {
                // 排除value值中的前进符号对逻辑的影响(简单实现)
                isValid = SPACE_PATTERN.matcher(keyAndValueStr.substring(keyLength + length, colonSignIndex)).matches();
                if (isValid) {
                    String valueStr = keyAndValueStr.substring(colonSignIndex + delimiter.length()).trim();
                    // 脱敏粒度 至 简单的key-value
                    // 且，如果简单的key-value中,值是一个json样式的字符串，如:"key": "{\"key1\":\"value1\",\"key2\":\"value2\"}"
                    // 那么此粒度也不脱敏
                    if (StringUtils.startsWithAny(valueStr, DefenderConstant.OBJECT_PREFIX_SIGN,
                            DefenderConstant.ARRAY_PREFIX_SIGN, DefenderConstant.JSON_STRING_SIGN)) {
                        return null;
                    }
                    int valueStrLength = valueStr.length();
                    // 如果这个字段刚好处于json的最后面，那么可能会多截取到【}】或【}]】之类的，所以需要判断值是String还是Number
                    int substringStartIndex = isBackslashQuotation ?
                            (valueStr.startsWith(DefenderConstant.BACKSLASH_QUOTATION_SIGN) ? 2 : 0)
                            : (valueStr.startsWith(DefenderConstant.QUOTATION_SIGN) ? 1 : 0);
                    int substringEndIndex = isBackslashQuotation ?
                            (valueStr.startsWith(DefenderConstant.BACKSLASH_QUOTATION_SIGN) ?
                                    valueStr.lastIndexOf(DefenderConstant.BACKSLASH_QUOTATION_SIGN) : valueStrLength)
                            : (valueStr.startsWith(DefenderConstant.QUOTATION_SIGN) ?
                            valueStr.lastIndexOf(DefenderConstant.QUOTATION_SIGN) : valueStrLength);
                    valueStr = valueStr.substring(substringStartIndex, substringEndIndex);
                    strideFlag[0] = KeyValueDelimiter.COLON;
                    return valueStr;
                }
            }
        }
        
        /// 如果不是使用上面的key-value分隔符，那么返回null
        return null;
    }
    
    public boolean getCompatBackslash() {
        return compatBackslash;
    }
    
    @Override
    public boolean support(LogEvent event) {
        return true;
    }
    
    /**
     * 本次key-value使用的脱敏方式： :  =  ->
     */
    public enum KeyValueDelimiter {
        
        /** : (同json key-value分隔符) */
        COLON,
        
        /** = */
        EQUAL,
        
        /** -> */
        ARROW
    }
}
